package nl.nhl.idp.Components.Network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import nl.nhl.idp.Components.Database.SensorData;

/**
 * Class that handles the sending of data to the guest applications
 * @author Mark van der Wal
 *
 */
public class DataServer extends Thread
{
	// constants
	private final int DATA_PORT = 1338;
	
	// data members
	private DataSender mDataSender;
	private List<DataConnection> mConnections;
	private ServerSocket mDataServer;
	private Queue<SensorData> mOutQueue;
	
	private boolean mIsListening;
	
	/**
	 * Constructor
	 */
	public DataServer()
	{
		mOutQueue = new LinkedList<SensorData>();
		mConnections = new ArrayList<DataConnection>();
	}
	
	/**
	 * Checks if the dataserver is listening
	 * @return true if the dataserver is listening
	 */
	public boolean IsListening() { return mIsListening; }
	
	/**
	 * Removes all disconnected users
	 */
    public void RemoveDisconnectedUsers()
    {
        // if the there are no clients then do not continue
        if(mConnections.size() < 1) return;
        
        // Critical section
        synchronized(mConnections)
        {
            // let's check all connected clients
            for(Iterator<DataConnection> it = mConnections.iterator(); it.hasNext();)
            {
            	DataConnection sc = it.next();
             
                 // if the client isn't connected anymore remove it
                if(!sc.IsConnected())
                {
                	// stop connection
                	sc.Stop();
                	
                    // remove the client
                    it.remove();
                }
            }
        }
    }
    
    /**
     * Checks if there are clients connected
     * @return true if clients are connected
     */
    public synchronized boolean ClientsConnected()
    {
    	return mConnections.size() > 0;
    }
	
    /** start the dataserver
     * 
     * @return true if it succeeded
     */
	public boolean Start()
	{
        try
        {           
            // create the network input output thread
        	mDataServer = new ServerSocket(DATA_PORT);
        	mDataSender = new DataSender(this);
        	mOutQueue.clear();
        	
        	mIsListening = true;
        	
        	// start threads
            this.start();
            mDataSender.start();

            return true;
        }
        catch(Exception e)
        {
        	// bla bla
        }
		
		return false;
	}
	
	/**
	 * Stop the dataserver
	 */
	public synchronized void terminate()
	{
		mIsListening = false;
		
		try 
		{
			mDataServer.close();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		
        synchronized(mConnections)
        {
            // let's check all connected clients
            for(Iterator<DataConnection> it = mConnections.iterator(); it.hasNext();)
            {
            	DataConnection sc = it.next();
                        
                // stop the connection
            	sc.Stop();
            	
                // remove the client
                it.remove();
            }
        }
        
		mDataSender.terminate();
	}
	
	/**
	 * Send data to the clients
	 * @param data we want to send
	 */
	public void SendData(SensorData data)
	{
		try 
		{
			mOutQueue.offer(data);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * Sending byte array data to the connections
	 * @param data
	 */
	private void SendToConnections(byte[] data)
	{
		// for all connections send data
        synchronized(mConnections)
        {
            // destroy all clients
            for(Iterator<DataConnection> it =  mConnections.iterator(); it.hasNext(); )
            {
                DataConnection sc = it.next();
                
                try 
                {
					sc.getOutputStream().write(data);
				} 
                catch (IOException e) 
				{
					e.printStackTrace();
					sc.SetConnected(false);
				}
            }
        }
	}
	
	@Override
	public void run()
	{	
		Socket client = null;
		DataConnection conClient = null;
		
		while(mIsListening)
		{		
			try 
			{
				// accept clients
				client = mDataServer.accept();
				client.setTcpNoDelay(true);

		        synchronized(mConnections)
		        {
					// add to the list
		        	conClient = new DataConnection(client);
		        	conClient.SetConnected(true);
					mConnections.add(conClient);
		        }
				
			} 
			catch (IOException e) 
			{
				if(mIsListening)
				{
					this.terminate();
				}
			}
		}
	}

	/**
	 * Handling the sending of data to the guest applications
	 */
	private class DataSender extends Thread
	{
		private final int INPUT_INTERVAL = 1000;
		
	    private long mCurrentTime;
	    private long mLastTime;
	    private long mDeltaTime;
	    private long mDataTimer;
	    private DataServer mParent;

		private boolean mIsRunning;
		
		public DataSender(DataServer parent)
		{
			mParent = parent;
			mIsRunning = false;
		}
		
		public void start()
		{
			mIsRunning = true;
			super.start();
		}
		
		public void terminate()
		{
			mIsRunning = false;
		}
		
		@Override
		public void run()
		{
			SensorData dummy = null;
			
			while( mIsRunning )
			{
				mParent.RemoveDisconnectedUsers();
				
				if(mConnections.size() > 0)
				{
		            // get the deltaTime
		            mCurrentTime = System.currentTimeMillis();
		            mDeltaTime = mCurrentTime - mLastTime;
		            mDataTimer += mDeltaTime;
		            
		            if( mDataTimer > INPUT_INTERVAL)
		            {            	
						if((dummy = mOutQueue.poll()) != null)
						{
							mParent.SendToConnections(ProtocolEncoder.EncodeSensors(dummy));
						}
						
		            	// check if there are too many messages in the queue
		            	if(mOutQueue.size() > 10)
		            	{
		            		mOutQueue.clear();
		            	}
						
						// reset the timer
		            	mDataTimer -= INPUT_INTERVAL;
		            }
		            
		            mLastTime = mCurrentTime;
				}
			}
		}
	}
}
